#include "editabletimelineitem.h"

#include <QStringBuilder>
#include <QMenu>
#include <QToolTip>
#include <QGraphicsSceneMouseEvent>
#include <QMessageBox>
#include "actiontimeline.h"

#define INFO_WIDTH 100


/*********************************************************/
/*             EDITABLE TIMELINE ITEM CLASS              */
/*********************************************************/
GUIEditableTimelineItem::GUIEditableTimelineItem(int timestamp, GUIActionTimeline* timeline, QGraphicsItem* parent) :
    QAbstractGraphicsShapeItem(parent),
    _timestamp(timestamp),
    _oldTimestamp(timestamp),
    _dragging(false),
    _timeline(timeline),
    _upperBoundOption(NO_SCROLL_PAST_VISIBLE),
    _lowerBoundOption(NO_SCROLL_PAST_VISIBLE),
    _contextTip("")
{
    this->setPos(timestamp, 0);
    setFlag(QGraphicsItem::ItemIsMovable);
    setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
}

void GUIEditableTimelineItem::setTimestamp(int msec){
    //Both the position of this item and the timestamp need to be updated
    //Calling setPos() will set the position, fire off the itemChanged()
    //event, and update the timestamp (see itemChanged())
    setPos(msec, 0);
}

int GUIEditableTimelineItem::timestamp() const{
    return _timestamp;
}

void GUIEditableTimelineItem::setContextTip(QString text){
    _contextTip = text;
}

QString GUIEditableTimelineItem::contextTip() const{
    return _contextTip;
}

void GUIEditableTimelineItem::setUpperMovementBound(BoundaryOption option){
    _upperBoundOption = option;
}

void GUIEditableTimelineItem::setLowerMovementBound(BoundaryOption option){
    _lowerBoundOption = option;
}

void GUIEditableTimelineItem::mousePressEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mousePressEvent(event);

    if(event->button() == Qt::LeftButton){
        _oldTimestamp = _timestamp;
        _dragging = true;

        QToolTip::showText(event->screenPos(), QString::number(MSEC_TO_SECF(_timestamp), 'f', 2) % "s");
    }
    else if(event->button() == Qt::RightButton){
        //open the context menu
        QMenu *menu = new QMenu;
        menu->addAction("Edit " % _contextTip, this, SLOT(onContextEdit()));
        menu->addAction("Delete " % _contextTip, this, SLOT(onContextDelete()));
        menu->exec(event->screenPos());
    }
}

void GUIEditableTimelineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mouseReleaseEvent(event);

    if(event->button() == Qt::LeftButton){
        _dragging = false;

        //Round the position to the nearest 10 msec
        int asInt = pos().toPoint().x();
        int remainder = asInt % 10;

        if(remainder >= 5)
            QGraphicsItem::setPos(pos().x() + (10 - remainder), 3);
        else
            QGraphicsItem::setPos(pos().x() - remainder, 3);

        if(_timestamp != _oldTimestamp)
            emit moved(_timestamp, _oldTimestamp);
    }
}

void GUIEditableTimelineItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    QGraphicsItem::mouseMoveEvent(event);

    if(_dragging){
        //Display a tooltip with information about how far the item is
        //being dragged along the timeline
        int diff = _timestamp - _oldTimestamp;
        QString sign = (diff >= 0) ? "s (+" : "s (";
        QString infoText = QString::number(MSEC_TO_SECF(_timestamp), 'f', 2)
                % sign % QString::number(MSEC_TO_SECF(diff), 'f', 2) % "s)";
        QToolTip::showText(event->screenPos(), infoText);
    }
}

void GUIEditableTimelineItem::onContextEdit(){
    emit editRequest(_timestamp);
}

void GUIEditableTimelineItem::onContextDelete(){
    emit deleteRequest(_timestamp);
}


QVariant GUIEditableTimelineItem::itemChange(GraphicsItemChange change, const QVariant &value){
    if(change == ItemPositionChange){
        //A position change is going to occur.  Restrict the change by allowing it to
        //only move in the horizontal direction.
        QPoint newPos = value.toPoint();
        newPos.setY(0);

        if(_dragging){
            //Calculate if it is past the scroll boundaries when we are dragging
            int upperBound = ((_timeline->width() - INFO_WIDTH) / _timeline->curScale()) + _timeline->offset();
            int lowerBound = _timeline->offset();

            if(newPos.x() < 0){
                newPos.setX(0);
            }
            else if(newPos.x() > upperBound){
                if(_upperBoundOption == SCROLL_PAST_VISIBLE){

                }
                else if(_upperBoundOption == NO_SCROLL_PAST_VISIBLE){
                    newPos.setX(upperBound);
                }
            }
            else if(newPos.x() < lowerBound){
                if(_lowerBoundOption == SCROLL_PAST_VISIBLE){


                }
                else if(_lowerBoundOption == NO_SCROLL_PAST_VISIBLE){
                    newPos.setX(lowerBound);
                }
            }
        }

        return newPos;
    }
    else if(change == ItemPositionHasChanged){
        //A position change has occured. Update the timestamp and tool tip.
        //Because this key frame is tied to the scale and position of its
        //parent motion track, its position is the time in msec.  This is
        //different than the ruler, which, in order to have an "infinite"
        //timeline, is simply a rectangle taking up only the space on the
        //screen (a fixed width/scale), and the scale simulated on painting
        //and mouse events.
        _timestamp = this->pos().x();
        //qDebug() << "Updated timestamp";
    }

    return QGraphicsItem::itemChange(change, value);
}


/*********************************************************/
/*                 TIMELINE TRACK CLASS                  */
/*********************************************************/
GUITimelineTrack::GUITimelineTrack(RobotActionTrack* track, EditDialog* dialog,
                                   GUIActionTimeline* timeline, QGraphicsItem* parent):
    QGraphicsRectItem(parent),
    _timeline(timeline),
    _track(track),
    _editDialog(dialog)
{
    //Any way to enforce that the action the track belongs to is the same action
    //loaded in the timeline?
    _editDialog->loadAction(timeline->action());

    connect(_track->trackWrapper(), SIGNAL(itemCreated(int)), this, SLOT(onModelItemCreated(int)));
    connect(_track->trackWrapper(), SIGNAL(itemDeleted(int)), this, SLOT(onModelItemDeleted(int)));
    connect(_track->trackWrapper(), SIGNAL(itemMoved(int,int)), this, SLOT(onModelItemMoved(int,int)));
    connect(_editDialog, SIGNAL(finished(int)), this, SLOT(onEditFinished(int)));
}

void GUITimelineTrack::addItem(GUIEditableTimelineItem *item){
    item->setParentItem(this);

    //Listen to the events
    connect(item, SIGNAL(editRequest(int)), this, SLOT(onEditRequest(int)));
    connect(item, SIGNAL(deleteRequest(int)), this, SLOT(onDeleteRequest(int)));
    connect(item, SIGNAL(moved(int,int)), this, SLOT(onGUIItemMoved(int,int)));
}

GUIEditableTimelineItem* GUITimelineTrack::findItemAtTime(int msec){
    //Search for the item.  Should we be maintaining a map for fast search?
    QList<QGraphicsItem*> children = this->childItems();
    for(int i = 0; i < children.length(); i++){
        if(((GUIEditableTimelineItem*)children[i])->timestamp() == msec)
            return ((GUIEditableTimelineItem*)children[i]);
    }

    return NULL;
}

void GUITimelineTrack::onEditRequest(int timestamp){
    emit editingItem(timestamp);

    _editDialog->prepDialog(timestamp);
    _editDialog->show();
}

void GUITimelineTrack::onEditFinished(int result){
    emit editingFinished(_editDialog->timestamp());
}

void GUITimelineTrack::onDeleteRequest(int timestamp){
    GUIEditableTimelineItem* item = findItemAtTime(timestamp);
    if(item != NULL){
        //Pop up a delete confirmation.  If it is confirmed,
        //Call the onDeleteConfirmed function in the subclasses
        //Use a QMessageBox to confirm the deletion of the key frame
        QMessageBox confirmation;
        confirmation.setWindowFlags(confirmation.windowFlags() | Qt::WindowStaysOnTopHint);
        confirmation.setText(item->contextTip() % " Deletion");
        confirmation.setInformativeText("Are you sure you want to delete this " % item->contextTip() % "?");
        confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
        confirmation.setDefaultButton(QMessageBox::No);

        int ret = confirmation.exec();
        if(ret == QMessageBox::Yes){
            //Call the virtual function to have the subclass delete whatever item is
            //associated with this dialog
            onDeleteConfirmed(item);
        }
    }
}

void GUITimelineTrack::onGUIItemMoved(int newTimestamp, int oldTimestamp){
    qDebug() << "Item moved in GUI";

    //Block signals so that the onModelItemMoved function isn't
    //called superflously (the item has already been moved in the
    //GUI in this case.)
    _track->trackWrapper()->blockSignals(true);
    onItemMovedInGUI(newTimestamp, oldTimestamp);
    _track->trackWrapper()->blockSignals(false);
}

void GUITimelineTrack::onModelItemCreated(int timestamp){
    //Create a new item
    qDebug() << "Item created in model";

    addItem(createItem(timestamp));
}

void GUITimelineTrack::onModelItemDeleted(int timestamp){
    qDebug() << "Item deleted from model";

    //Delete an item
    GUIEditableTimelineItem* curItem;
    QList<QGraphicsItem*> children = this->childItems();
    for(int i = 0; i < children.count(); i++){
        curItem = (GUIEditableTimelineItem*)children[i];

        if(curItem->timestamp() == timestamp){
            scene()->removeItem(curItem);
            delete curItem;
        }
    }
}

void GUITimelineTrack::onModelItemMoved(int newTimestamp, int oldTimestamp){
    qDebug() << "Item moved in model";

    //Move the item
    QList<QGraphicsItem*> children = this->childItems();
    for(int i = 0; i < children.count(); i++){
        if(((GUIEditableTimelineItem*)children[i])->timestamp() == oldTimestamp){
            ((GUIEditableTimelineItem*)children[i])->setTimestamp(newTimestamp);
            return;
        }
    }
}


/*********************************************************/
/*                   EDIT DIALOG CLASS                   */
/*********************************************************/
EditDialog::EditDialog(QWidget *parent):
    QDialog(parent),
    _timestamp(0),
    _action(NULL),
    _deleteButton(new QPushButton(tr("Delete"))),
    _contextTip("Item")
{
    _deleteButton->setAutoDefault(false);
    _deleteButton->setVisible(false);
    connect(_deleteButton, SIGNAL(clicked()), this, SLOT(onDeleteBtnClicked()));

    //Set the window so that it will stay on top of the main action creator window
    this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);
}

void EditDialog::setContextTip(QString tip){
    _contextTip = tip;
}

QString EditDialog::contextTip() const{
    return _contextTip;
}

int EditDialog::timestamp() const{
    return _timestamp;
}

void EditDialog::loadAction(RobotAction *action){
    _action = action;
}

void EditDialog::prepDialog(int timestamp){
    _timestamp = timestamp;
}

void EditDialog::accept(){
    if(_action == NULL){
        //We don't have an action loaded, so this will bring up the
        //new action dialog and create a new action. The dialog is
        //modal, so control should return to this function after the
        //new action is loaded.
        emit needAction();
    }

    //Call the virtual function to have the subclass save this item
    saveItem();
    QDialog::accept();
}

void EditDialog::onDeleteBtnClicked(){
    qDebug() << "Delete button clicked";
    if(_action == NULL)
        return;

    //Use a QMessageBox to confirm the deletion of the key frame
    QMessageBox confirmation;
    confirmation.setWindowFlags(confirmation.windowFlags() | Qt::WindowStaysOnTopHint);
    confirmation.setText(_contextTip % " Deletion");
    confirmation.setInformativeText("Are you sure you want to delete this " % _contextTip % "?");
    confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
    confirmation.setDefaultButton(QMessageBox::No);

    int ret = confirmation.exec();
    if(ret == QMessageBox::Yes){
        //Call the virtual function to have the subclass delete whatever item is
        //associated with this dialog
        deleteItem();
        QDialog::accept();
    }
    else{
        QDialog::reject();
    }
}


